﻿/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//Copyright (c) 2009 foldier inc.
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
//associated documentation files (the "Software"), to deal in the Software without restriction, including 
//without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
//copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 
//following conditions:
//
//The above copyright notice and this permission notice shall be included in all copies or substantial portions
//of the Software.
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 
//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
//LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
//OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 
//IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Data;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Net;
using System.Configuration;
using System.Collections.Generic;
using System.Text.RegularExpressions;

#if SILVERLIGHT
using System.Windows.Browser;
#else
using System.Web;
using System.Xml.XPath;
#endif

namespace foldier.API
{
    /// <summary>
    /// Summary description for FoldierOAuthConnector
    /// </summary>
    public class FoldierOAuthConnector
    {

#if SILVERLIGHT
        static public string APIServer = "http://www.foldier.com";
#else
        static public string APIServer = "http://www.foldier.com";
#endif   

        static public string RequestTokenPath = APIServer + "/oauth/GetRequestToken";
        static public string UserAuthorizationPath = APIServer + "/oauth/AuthorizeToken";
        static public string AccessTokenPath = APIServer + "/oauth/GetAccessToken";
        static public string SignedRequestPath = APIServer + "/api";

        #region MEMBER VARIABLES 

        FoldierOauth m_oauth;
        private string m_apiKey = string.Empty;
		private string m_secret = string.Empty;
        private string m_token = string.Empty;
        private string m_tokenSecret = string.Empty;
        private oAuthState m_oAuthState = oAuthState.idle;
		private long m_userID = 0;
		protected string m_lastError = string.Empty;

		#endregion
        
		#region PROPERTIES
        /// <summary>
        /// Gets OAuth authetication state
        /// </summary>
		public bool IsAuthenticated
        {
            get
            {
                if ((m_oAuthState != oAuthState.accessTokenGranted) || string.IsNullOrEmpty(m_token) || string.IsNullOrEmpty(m_tokenSecret))
                    return false;
                else return isAuthenticated();
            }
        }
        /// <summary>
        /// Gets or sets token value
        /// </summary>
        public string Token { get { return m_token; } set { m_token = value; } }
        /// <summary>
        /// Gets or sets token secret value
        /// </summary>
        public string TokenSecret { get { return m_tokenSecret; } set { m_tokenSecret = value; } }
        /// <summary>
        /// Gets or sets oAuthState value
        /// </summary>
        public oAuthState OAuthState
        {
            get { return m_oAuthState; }
            set { m_oAuthState = value; }
        }
        /// <summary>
        /// Gets userID
        /// </summary>
        public long UserID { get { return m_userID; } set { m_userID = value; } }

        #endregion

        #region CONSTRUCTORS

        /// <summary>
        ///     Constructor - Build an empty connector
        /// </summary>
        public FoldierOAuthConnector()
            : base()
        {
            initOAuthClient();
        }

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="apiKey">API key string</param>
		/// <param name="secret">Secret token</param>
		public FoldierOAuthConnector(string apiKey, string secret)
			: base()
		{
			m_apiKey = apiKey;
			m_secret = secret;
			initOAuthClient();
		}

        /// <summary>
        /// Constructor - Build a connection given the token
        /// </summary>
        /// <param name="oauth_token">OAuth token</param>
        /// <param name="oauth_token_secret">OAuth token secret</param>
        /// <param name="oauthTokenState">OAuth token state</param>
        public FoldierOAuthConnector(string oauth_token, string oauth_token_secret, oAuthState oauthTokenState)
            : base()
        {
            initOAuthClient();
            m_token = oauth_token;
            m_tokenSecret = oauth_token_secret;
            m_oAuthState = oauthTokenState;
        }

        #endregion

        #region PRIVATE/PROTECTED UTILITIES

        /// <summary>
        /// Initialize oAuth client
        /// </summary>
		private void initOAuthClient()
        {
            m_oauth = new FoldierOauth(m_apiKey, m_secret);
            if (m_oauth != null)
            {
                m_oauth.RequestTokenPath = RequestTokenPath;
                m_oauth.AccessTokenPath = AccessTokenPath;
            }
        }

        /// <summary>
        /// Retrieves the access token state
        /// </summary>
        /// <returns>bool</returns>
		private bool isAuthenticated()
        {
            if ((m_oAuthState != oAuthState.accessTokenGranted) || (m_oauth == null))
                return false;

            return true;
        }

        /// <summary>
        /// Convert the string date to its System.Date equivalent object
        /// </summary>
        /// <param name="dateString">Date string to convert</param>
        /// <returns>DateTime object</returns>
		protected DateTime safeDateTime( string dateString )
		{
			DateTime result = DateTime.Now;
			DateTime.TryParse( dateString, out result );
			return result;
		}

        #endregion

        #region PUBLIC API

		/// <summary>
		/// Authenticates the given token
		/// </summary>
		/// <param name="token">OAuth token</param>
		/// <param name="tokenSecret">OAuth token secret</param>
		public void authenticate(string token, string tokenSecret, long userID)
		{
			m_token = token;
			m_tokenSecret = tokenSecret;
			m_userID = userID;

			m_oAuthState = oAuthState.accessTokenGranted;

		}

        /// <summary>
        /// Start oAuth pipeline process (Step 1 and 2):
        /// - RequestToken
        /// - Authorization
        /// </summary>
        /// <returns>
        /// The URL to send the redirect the user to authorize the application;
        /// string.Empty if an error occurred.
        /// </returns>
		public string startAuthenticate()
        {
            if ((m_oAuthState == oAuthState.accessTokenGranted) || (m_oauth == null))
                return string.Empty;

            // 1- Get Request token
            string reqTokDetails = m_oauth.customerRequestToken(string.Empty, string.Empty);

            string pattern = @"^oauth_token=[a-zA-z\d-_.~%]+&oauth_token_secret=[a-zA-z\d-_.~%]+";
            Regex reg = new Regex(pattern);
            Match mat = Regex.Match(reqTokDetails, pattern);

            if (!mat.Success)
                return string.Empty;

            string[] requestSplit = mat.Value.Split(new char[] { '&', '=' });

            m_token = requestSplit[1];
            m_tokenSecret = requestSplit[3];
            m_oAuthState = oAuthState.tokenRequested;

            // 2 - Authorize Token
            return string.Format("{0}?oauth_token={1}", UserAuthorizationPath, m_token);
        }

        /// <summary>
        /// Ask foldier for the definitive OAuth access token
        /// </summary>
        /// <returns>OAuth authentication state</returns>
		public bool GetAccessToken()
        {
            // This should check only for tokenAuthorized. 
            // Nevertheless we allow also the tokenRequested state, since the callback page doesn't know 
            // whether the access has been granted from the user (foldier doesn't notify this).
            if (((m_oAuthState != oAuthState.tokenAuthorized) && (m_oAuthState != oAuthState.tokenRequested))
                                                || (m_oauth == null))
                return false;

            string accTokDetails = m_oauth.getAccessToken(string.Empty, string.Empty, m_token, m_tokenSecret);

            string pattern = @"^oauth_token=[a-zA-z\d-_.~%]+&oauth_token_secret=[a-zA-z\d-_.~%]+";
            Regex reg = new Regex(pattern);
            Match mat = Regex.Match(accTokDetails, pattern);

            if (!mat.Success)
                return false;

            string[] requestSplit = mat.Value.Split(new char[] { '&', '=' });
            m_token = requestSplit[1];
            m_tokenSecret = requestSplit[3];
            m_oAuthState = oAuthState.accessTokenGranted;

            // double check the validity of the token (the user might have choosen 'Deny'...)
            bool result = isAuthenticated();

            //make a call to API/info to retrieve the foldier user info (userID, Name, ProfileURL)
            //and authenticate the tokens
            if (result)
            {
                Info info = new Info(this);
                UserInfo userInfo = info.get();
                this.authenticate(m_token, m_tokenSecret, userInfo.UserID);
            }

            return result;
        }

		/// <summary>
        /// Retrieve the ItemLink items within the given result string
		/// </summary>
		/// <param name="result">ItemLink collection string</param>
        /// <returns>Returns an array of ItemLink objects</returns>
		public ItemLink[] resultItemLinks(string result)
		{
			XDocument xmlItems = new XDocument();

			try
			{
				xmlItems = XDocument.Parse(result);
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}

			try
			{
				var query = from item in xmlItems.Descendants("item")
							select new ItemLink
							{
								ID = (long)item.Attribute("id"),
								Name = ((string)item.Element("title")).Trim(),
								Link = ((string)item.Element("link")).Trim(),
								Comments = ((string)item.Element("comments")).Trim(),
								SourceName = ((string)item.Element("source")).Trim(),
								Description = ((string)item.Element("description")).Trim(),
								Thumbnail = ((string)item.Element("thumb")).Trim(),
								Modified = safeDateTime(item.Attribute("modified").Value)
							};

				return query.ToArray<ItemLink>();
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}
		}

		/// <summary>
        /// Retrieve the ItemFolder items within the given result string
		/// </summary>
        /// <param name="result">ItemFolder collection string</param>
        /// <returns>Returns an array of ItemFolder objects</returns>
		public ItemFolder[] resultItemFolders(string result)
		{
			XDocument xmlItems = new XDocument();

			try
			{
				xmlItems = XDocument.Parse(result);
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}
			try
			{
				var query = from item in xmlItems.Descendants("item")
							select new ItemFolder
							{
								ID = (long)item.Attribute("id"),
								Name = ((string)item.Element("title")).Trim(),
								Link = ((string)item.Element("link")).Trim(),
								Comments = ((string)item.Element("comments")).Trim(),
								SourceName = ((string)item.Element("source")).Trim(),
								Description = ((string)item.Element("description")).Trim(),
								Thumbnail = ((string)item.Element("thumb")).Trim(),
								Modified = safeDateTime(item.Attribute("modified").Value)
							};

				return query.ToArray<ItemFolder>();
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}
		}

		/// <summary>
        ///		Retrieve the ItemSPression items within the given result
		/// </summary>
        /// <param name="result">ItemSPression collection string</param>
        /// <returns>Returns an array of ItemSPression objects</returns>
		public ItemSPression[] resultItemSPressions(string result)
		{
			XDocument xmlItems = new XDocument();

			try
			{
				xmlItems = XDocument.Parse(result);
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}
			try
			{
                var query = from item in xmlItems.Descendants("item")
							where item.Attribute("type").Value =="ItemSPress"
							select new ItemSPression
							{
								ID = (long)item.Attribute("id"),
								Name = ((string)item.Element("title")).Trim(),
								Link = ((string)item.Element("link")).Trim(),
								Comments = ((string)item.Element("comments")).Trim(),
								SourceName = ((string)item.Element("source")).Trim(),
								Description = ((string)item.Element("description")).Trim(),
								Thumbnail = ((string)item.Element("thumb")).Trim(),
								CollectorID = (long) ( item.Element("spress_info").Element("collector").Attribute("id") ),
								Modified = safeDateTime(item.Attribute("modified").Value)
							}
							;

				return query.ToArray<ItemSPression>();
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}
		}

		/// <summary>
        /// Retrieve a single item (link) within the given result
		/// </summary>
        /// <param name="result">Item collection string</param>
		/// <returns>Returns a BaseItem object</returns>
		public BaseItem resultItem(string result)
		{
			XDocument xmlItems = new XDocument();

			try
			{
				xmlItems = XDocument.Parse(result);
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}
			try
			{
				var query = from item in xmlItems.Descendants("item")
							select new BaseItem
							{
								ID = (long)item.Attribute("id"),
								Name = ((string)item.Element("title")).Trim(),
								Link = ((string)item.Element("link")).Trim(),
								Comments = ((string)item.Element("comments")).Trim(),
								SourceName = ((string)item.Element("source")).Trim(),
								Description = ((string)item.Element("description")).Trim(),
								Thumbnail = ((string)item.Element("thumb")).Trim(),
								Modified = safeDateTime( item.Attribute( "modified" ).Value )
							}
							;

				return query.FirstOrDefault();
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}
		}

		/// <summary>
        /// Retrieve a single smartfolder within the given result
		/// </summary>
        /// <param name="result">Smartfolder collection string</param>
        /// <returns>Returns an ItemFolder object</returns>
		public ItemFolder resultItemFolder(string result)
		{
			XDocument xmlItems = new XDocument();

			try
			{
				xmlItems = XDocument.Parse(result);
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}
			try
			{
				var query = from item in xmlItems.Descendants("item")
							where item.Attribute("type").Value == "ItemFolder"
							select new ItemFolder
							{
								ID = (long)item.Attribute("id"),
								Name = ((string)item.Element("title")).Trim(),
								Link = ((string)item.Element("link")).Trim(),
								Comments = ((string)item.Element("comments")).Trim(),
								SourceName = ((string)item.Element("source")).Trim(),
								Description = ((string)item.Element("description")).Trim(),
								Thumbnail = ((string)item.Element("thumb")).Trim(),
								Modified = safeDateTime( item.Attribute( "modified" ).Value )
							};

				return query.FirstOrDefault();
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}
		}

		/// <summary>
        ///	Retrieve a single sPression within the given result
		/// </summary>
        /// <param name="result">sPression collection string</param>
        /// <returns>Returns an ItemSPression object</returns>
		public ItemSPression resultItemSPression(string result)
		{
			XDocument xmlItems = new XDocument();

			try
			{
				xmlItems = XDocument.Parse(result);
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}
			try
			{
				var query = from item in xmlItems.Descendants("item")
							where item.Attribute("type").Value == "ItemSPress"
							select new ItemSPression
							{
								ID = (long)item.Attribute("id"),
								Name = ((string)item.Element("title")).Trim(),
								Link = ((string)item.Element("link")).Trim(),
								Comments = ((string)item.Element("comments")).Trim(),
								SourceName = ((string)item.Element("source")).Trim(),
								Description = ((string)item.Element("description")).Trim(),
								Thumbnail = ((string)item.Element("thumb")).Trim(),
								CollectorID = (long)(item.Element("spress_info").Element("collector").Attribute("id")),
								Modified = safeDateTime(item.Attribute("modified").Value)
							};

				return query.FirstOrDefault();
			}
			catch (Exception exErr)
			{
				m_lastError = exErr.Message;
				return null;
			}
		}

        /// <summary>
        ///		Retrieve the user info items within the given result
        /// </summary>
        /// <param name="result">UserInfo collection string</param>
        /// <returns>Returns a UserInfo objects</returns>
        public UserInfo resultUserInfo(string result)
        {
            XDocument xmlUsers = new XDocument();

            try
            {
                xmlUsers = XDocument.Parse(result);
            }
            catch (Exception exErr)
            {
                m_lastError = exErr.Message;
                return null;
            }
            try
            {
                var query = from user in xmlUsers.Descendants("user")
                            //where user.Attribute("type").Value == "ItemSPress"
                            select new UserInfo
                            {
                                UserID = (int)user.Attribute("id"),
                                UserName = ((string)user.Element("name")).Trim(),
                                UserLastName = ((string)user.Element("lastName")).Trim(),
                                ProfileUrl = ((string)user.Element("profileUrl")).Trim()
                            }
                            ;

                return query.FirstOrDefault();
            }
            catch (Exception exErr)
            {
                m_lastError = exErr.Message;
                return null;
            }
        }
        
        /// <summary>
        /// Retrieve the item(s) from user account
        /// </summary>
        /// <param name="userID">ID of the user account</param>
        /// <param name="itemID">ID of the item, 0 to get all the items</param>
        /// <returns>Result string containing item(s)</returns>
		public string getItems(int userID, long itemID)
        {
            if ((m_oAuthState != oAuthState.accessTokenGranted) || (m_oauth == null))
                return string.Empty;

            if (itemID > 0)
                m_oauth.SignedRequestPath = string.Format("{0}/{1:x}/items/{2:x}", SignedRequestPath, userID, itemID);
            else
                m_oauth.SignedRequestPath = string.Format("{0}/{1:x}/items", SignedRequestPath, userID);

            Dictionary<string, string> headers = new Dictionary<string, string>();

            return m_oauth.sendSignedRequest(string.Empty, string.Empty, headers, m_token, m_tokenSecret);
        }

        /// <summary>
        /// Send a generic API request to foldier
        /// </summary>
        /// <param name="Url">Url of the call to the API</param>
        /// <param name="parameters">Parameters of the querystring, string.Empty if none</param>
        /// <returns>String of collected elements</returns>
        public string genericApiCall(string Url, string parameters)
        {
            if ((m_oAuthState != oAuthState.accessTokenGranted) || (m_oauth == null))
                return string.Empty;

			if (Url.StartsWith("http"))
				m_oauth.SignedRequestPath = Url;
			else
				m_oauth.SignedRequestPath = String.Format("{0}/{1}", SignedRequestPath, Url.TrimStart('/') );

            Dictionary<string, string> headers = new Dictionary<string, string>();
            return m_oauth.sendSignedRequest(string.Empty, parameters, headers, m_token, m_tokenSecret);
        }
        
        #endregion

    }
}
